package ReadWriteFieldsRelations;

import util.Filter;
import util.GAConfig;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;

import org.objectweb.asm.tree.MethodInsnNode;

import GA.MethodDeclared;



import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.*;
import java.io.*;
import java.lang.reflect.Constructor;
public class MethodRelations implements Opcodes {

  private  List<Class<?>> classes= new LinkedList<Class<?>>();;
  public static final String lineSep = System.getProperty("line.separator");
  protected final Map<Class<?>, Map<String, ReadWriteFields>> fieldReadWrites = new LinkedHashMap<Class<?>, Map<String, ReadWriteFields>>();  
//  protected final Map<Class<?>, Map<String, ReadWriteFields>> fieldConstructorReadWrites = new LinkedHashMap<Class<?>, Map<String, ReadWriteFields>>();  

  protected final Map<Class<?>, Map<Method, ReadWriteMethods>> dependences = new LinkedHashMap<Class<?>, Map<Method, ReadWriteMethods>>();  
  public static Map  <String,Integer> fieldRead = new HashMap();
  public static Map <String,Integer>fieldsWrite = new HashMap();
 
  public MethodRelations(List<String> classes) throws ClassNotFoundException {
	  for(int i=0;i<classes.size();i++){
		  Class<?>   clazz =  Class.forName(classes.get(i));
		  clazz = Class.forName(classes.get(i));
	    this.classes.add(clazz);
	  }
	  }
	  
//  public MethodRelations(Collection<Class<?>> classes) {
//    this.classes = classes;
//  }
//  
public Map<Class<?>, Map<String, ReadWriteFields>> getFieldReadWrites(){
	return fieldReadWrites;
}
  public void buildRelations() throws IOException {
    //analyze each class
    for(Class<?> cls : classes) {
      ClassReader cr = new ClassReader(cls.getName());
      ClassNode cn = new ClassNode();
      cr.accept(cn, ClassReader.SKIP_FRAMES);
      Map<String, ReadWriteFields> methodReadWrites = analyzeClass(cls, cn);
 //     Map<String, ReadWriteFields> constructorReadWrites = analyzeClassConstructor(cls, cn);
      this.fieldReadWrites.put(cls, methodReadWrites);
      
    }
   this.computePrivateDependence();
    this.computeDependence();
   }
  public void computePrivateDependence (){
	  boolean flag=false;
	  while(!flag){
		  flag=true;
	    for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		      Map<Method, ReadWriteFields> methodAndReadWrites = entry.getValue();
		      for(Entry<Method, ReadWriteFields> mfields: methodAndReadWrites.entrySet()) {
		    	  String mName=mfields.getKey().toString();
		    	  ReadWriteFields fields = mfields.getValue();
		    	  Set<Method> methodsCalls=fields.calls();
		    	  for(Method MethodCall : methodsCalls) {
		    		  String mNameCall=MethodCall.toString();
		    //		  if(!DefaultReflectionFilter.isPrivate(MethodCall)||(mName.equals(mNameCall)))
		    		  if(mName.equals(mNameCall))
		    			  continue;
		    		  int i=-10000;
		    		  Set<String> readcallMethod = methodAndReadWrites.get(MethodCall).readFields.keySet();
		    		  for(String readField : readcallMethod) {
		    			  if(fields.readFieldExist(readField)){

			    			  fields.read(readField);
			    			  flag=false;
		    		      }
		    		  }
		    		  Set<String> writecallMethod = methodAndReadWrites.get(MethodCall).writeFields.keySet();
		    		  for(String writeField : writecallMethod) {
		    			  if(fields.writeFieldExist(writeField)){
		    				  fields.write(writeField);
		    				  flag=false;
		    			  }
		    		  }		    		
		    		  
		    		  
		    	  }
		      }
	    }
	  }
  }
  public Map getMethodsDependencies(String className,String methodName, String type){
	  Map MethodDependenciesList= new HashMap();
	    for(Entry<Class<?>, Map<Method, ReadWriteMethods>> entry : this.dependences.entrySet()) {	     
	        String cName=entry.getKey().getName();
	        if(cName.equals(className)){
	        	 Map<Method, ReadWriteMethods> methodList = entry.getValue();
	        	 for(Entry<Method, ReadWriteMethods> mDependence: methodList.entrySet()) {
	        		 String mName=mDependence.getKey().getName();
	        		 if(mName.equals(methodName.trim())){
	        			 ReadWriteMethods allMethods = mDependence.getValue();
	        			 if( type.equals("getReadMethods"))
	        				 MethodDependenciesList= allMethods.getReadMethods();
	        			 else
	        				 MethodDependenciesList= allMethods.getWriteMethods();
	        			 break;
	        		 }
	        	 }
	        }
	    }	  
	  return MethodDependenciesList;
  }

  String showDependence() {
	    StringBuilder sb = new StringBuilder();
	    
	    for(Entry<Class<?>, Map<Method, ReadWriteMethods>> entry : this.dependences.entrySet()) {
	        sb.append("Class: ");
	        sb.append(entry.getKey());

	        sb.append(this.lineSep);
	        Map<Method, ReadWriteMethods> methodSummary = entry.getValue();
	        for(Entry<Method, ReadWriteMethods> mfields: methodSummary.entrySet()) {
	    		  if(Filter.isPrivate(mfields.getKey()))
	    			  continue;
	       	 sb.append(this.lineSep);
	          sb.append("  method: ");
	          sb.append(mfields.getKey());       
	          sb.append(this.lineSep);
	          sb.append("     Read Methods Dependences:");
	          sb.append(this.lineSep);
	          ReadWriteMethods allMethods = mfields.getValue();
	          Map readMethods=allMethods.getReadMethods();
	          Set set = readMethods.keySet();  
	          Iterator iter = set.iterator();  
	            
	          while (iter.hasNext()) {  
	              String className = (String)iter.next();  
	             List methods = (List) readMethods.get(className);  
	             sb.append("        " + className +"   ");
	             sb.append(methods);
	             sb.append(this.lineSep);
	          }
	          sb.append(this.lineSep);
	          sb.append("     Write Methods Dependences:");
	          sb.append(this.lineSep);
	          Map writeMethods=allMethods.getWriteMethods();
	           set = writeMethods.keySet();  
	           iter = set.iterator();  
	            
	          while (iter.hasNext()) {  
	              String className = (String)iter.next();  
	             List methods = (List) writeMethods.get(className);  
	             sb.append("        " + className +"   ");
	             sb.append(methods);
	             sb.append(this.lineSep);
	          }
	          

	        }
	        sb.append(this.lineSep + this.lineSep);
	      }

	       return sb.toString();
	  }
  
  public Map<Method, ReadWriteMethods> getMethodsReadFileds(String className) throws ClassNotFoundException, IOException {
	  Map<Method, ReadWriteMethods> methodReadWrites = new LinkedHashMap<Method, ReadWriteMethods>();
	  Class<?> clazz = Class.forName(className);

	  methodReadWrites=dependences.get( clazz );
	  
	  return methodReadWrites;
  }
 public  Map<Class<?>, Map<String, List<String>>> buildWriteFiledMethodDependence(){
	  Map<Class<?>, Map<String, List<String>>> writeFieldMethods = new LinkedHashMap<Class<?>, Map<String, List<String>>>();  
	  Map<String, List<String>> fieldMethod= new LinkedHashMap <String, List<String>>();
	
	  for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		  Class<?> cName=entry.getKey();
		  Map<Method, ReadWriteFields> methodSummary = entry.getValue();
		  fieldMethod= new LinkedHashMap <String, List<String>>();
		  for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {

			   Method mName=mfields.getKey();
			   if(Filter.isPrivate(mName)) continue;
			   ReadWriteFields fields = mfields.getValue();
			   List<String> methods=new LinkedList<String>();
		       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
		    	   String fieldName=writes.getKey().toString();
		    	    if(fieldMethod.get(fieldName) == null)
		    	    	fieldMethod.put(fieldName, new LinkedList<String>());
		    		List methodList = (List) fieldMethod.get(fieldName);
		    		methodList.add(mName.getName());
		       }       
		   }
		   writeFieldMethods.put(cName, fieldMethod);
	//	   fieldMethod.clear();
	  }
	 // System.out.print("");
	  return writeFieldMethods;
  }
 public  Map<Class<?>, Map<String, List<String>>> buildMethodsCallPrivate(String mPrivateName ){
	  Map<Class<?>, Map<String, List<String>>> writeFieldMethods = new LinkedHashMap<Class<?>, Map<String, List<String>>>();  
	  Map<String, List<String>> fieldMethod= new LinkedHashMap <String, List<String>>();
	
	  for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		  Class<?> cName=entry.getKey();
		  Map<Method, ReadWriteFields> methodSummary = entry.getValue();
		  fieldMethod= new LinkedHashMap <String, List<String>>();
		  for(Entry<Method, ReadWriteFields> mCalls: methodSummary.entrySet()) {

			   Method mName=mCalls.getKey();
			   if(Filter.isPrivate(mName)) continue;
			   ReadWriteFields fields = mCalls.getValue();
			   List<String> methods=new LinkedList<String>();
			   Set<Method> methdoCalls=fields.calls;
			   for(Method mCall : methdoCalls){
//				   System.out.print(mCall.getName());
				   if(!mPrivateName.trim().equals(mCall.getName())) continue;
		    	    if(fieldMethod.get(mPrivateName) == null)
		    	    	fieldMethod.put(mPrivateName, new LinkedList<String>());
		    		List methodList = (List) fieldMethod.get(mPrivateName);
		    		methodList.add(mName.getName());
			   }
 
		   }
		   writeFieldMethods.put(cName, fieldMethod);
	//	   fieldMethod.clear();
	  }
	 // System.out.print("");
	  return writeFieldMethods;
  

 }
  String showFieldReadWrites() {
    StringBuilder sb = new StringBuilder();
    
   for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
     sb.append("Class: ");
     sb.append(entry.getKey());

     sb.append(this.lineSep);
     Map<Method, ReadWriteFields> methodSummary = entry.getValue();
     for(Entry<Method, ReadWriteFields> mfields: methodSummary.entrySet()) {
    	 sb.append(this.lineSep);
       sb.append("Method: ");
       sb.append(mfields.getKey());       
       sb.append(this.lineSep);
       sb.append("Fields:");
       sb.append(this.lineSep);
       ReadWriteFields fields = mfields.getValue();
       sb.append("    All reads:" + this.lineSep);
       for(Entry<String, Integer> reads : fields.readFields.entrySet()) {
         sb.append("       " + reads.getKey()+ ", " + reads.getValue());
         if(fieldRead.containsKey(reads.getKey().toString())){
        	 String name=reads.getKey().toString();
        	 int totalnum=(Integer) fieldRead.get(name)+reads.getValue();
        	 fieldRead.put(name,totalnum);
         }
         else{
        	 int totalnum=reads.getValue();
        	 String name=reads.getKey().toString();
        	 fieldRead.put(name,totalnum);
     	 
         }
        	
    //     sb.append(this.lineSep);
       }
       sb.append(this.lineSep);
       sb.append("    All writes:" + this.lineSep);
       for(Entry<String, Integer> writes : fields.writeFields.entrySet()) {
         sb.append("       " + writes.getKey()+ ", " + writes.getValue());
         if(fieldsWrite.containsKey(writes.getKey().toString())){
        	 String name=writes.getKey().toString();
        	 int totalnum=(Integer) fieldsWrite.get(name)+writes.getValue();
        	 fieldsWrite.put(name,totalnum);
         }
         else{
        	 int totalnum=writes.getValue();
        	 String name=writes.getKey().toString();
        	 fieldsWrite.put(name,totalnum);
     	 
         }
        	 
 //        sb.append(this.lineSep);
       }
       sb.append(this.lineSep);
	       sb.append("All methods calls:" + this.lineSep);
	       sb.append("                "+ fields.calls());
	       sb.append(this.lineSep);
     }
     sb.append(this.lineSep + this.lineSep);
   }
   sb.append(this.lineSep + this.lineSep);
   sb.append("Total read fields");
   sb.append(this.lineSep);
   for (Map.Entry<String,Integer> entry : fieldRead.entrySet()) {
	   sb.append( entry.getKey()+"="+ entry.getValue());
	   sb.append(this.lineSep);
	}
   sb.append(this.lineSep + this.lineSep);
   sb.append("Total write fields");
   sb.append(this.lineSep);
   for (Map.Entry<String,Integer> entry : fieldsWrite.entrySet()) {
	   sb.append( entry.getKey()+"="+  entry.getValue());
	   sb.append(this.lineSep);
	}
    return sb.toString();
  }

  /**
   * All private methods below
   * */
  private Map<String, ReadWriteFields> analyzeClass(Class<?> cls, ClassNode cn) {
    Map<String, ReadWriteFields> methodReadWrites = new LinkedHashMap<String,ReadWriteFields>();
    //analyze each method
    List<MethodNode> methodNodes = cn.methods;    
    for(MethodNode methodNode : methodNodes) {
       Method method = this.getMethod(cls, methodNode);
    //	Method method= methodNode.
      if(method == null) {
       continue;
      }

 //     if(Filter.canUse(method)){
    	  ReadWriteFields readWrites = this.getReadWriteFields(cls, methodNode);
    	  methodReadWrites.put(method.getName(), readWrites);
    	  
      
 //     }
    }
    return methodReadWrites;
    
  }
  private Map<String, ReadWriteFields> analyzeClassConstructor(Class<?> cls, ClassNode cn) {
	    Map<String, ReadWriteFields> constructorReadWrites = new LinkedHashMap<String,ReadWriteFields>();
	    List<MethodNode> methodNodes = cn.methods;    
	    for(MethodNode methodNode : methodNodes) {
	        System.out.println("Method: "+methodNode.name );
	       
	      if(methodNode.name.equals("<init>")){
	       	  ReadWriteFields readWrites = this.getReadWriteFields(cls, methodNode);
	       	  constructorReadWrites.put(methodNode.name, readWrites);
	       	  continue;
	      }
	    }
	    return constructorReadWrites;
  }
  private ReadWriteFields getReadWriteFields(Class<?> cls, MethodNode methodNode) {
	
    ReadWriteFields readWrites = new ReadWriteFields();
    InsnList mlist = methodNode.instructions;
    for(int i = 0; i < mlist.size(); i++) {
      AbstractInsnNode insn = mlist.get(i);

      int opCode = insn.getOpcode();
   
      switch(opCode) {
      case GETFIELD:
          FieldInsnNode fieldGet = (FieldInsnNode)insn;
  
          readWrites.read(fieldGet.name);
          break;
        case PUTFIELD:
          FieldInsnNode fieldPut = (FieldInsnNode)insn;
          readWrites.write(fieldPut.name);
          break;
        case GETSTATIC:
          FieldInsnNode fieldStaticGet = (FieldInsnNode)insn;
          readWrites.read(fieldStaticGet.name);
          break;
        case PUTSTATIC:
          FieldInsnNode fieldStaticPut = (FieldInsnNode)insn;
          readWrites.write(fieldStaticPut.name);
          break;
        case INVOKEVIRTUAL:
          MethodInsnNode virtualMethod = (MethodInsnNode)insn;
          readWrites.call(this.getMethod(cls, virtualMethod.name, virtualMethod.desc));
          break;
        case INVOKESTATIC:
          MethodInsnNode staticMethod = (MethodInsnNode)insn;
          readWrites.call(this.getMethod(cls, staticMethod.name, staticMethod.desc));
          break;         
        case INVOKESPECIAL:
            MethodInsnNode privateMethod = (MethodInsnNode)insn;
            readWrites.call(this.getMethod(cls, privateMethod.name, privateMethod.desc));
            break;   
        }
    }
    
    return readWrites;
  }

  private Method getMethod(Class<?> cls, MethodNode methodNode) {
    return this.getMethod(cls, methodNode.name, methodNode.desc);
  }
  
  private Method getMethod(Class<?> cls, String methodName, String methodDesc)   {
  Method[] methods = cls.getDeclaredMethods();
//	  Method[] methods = cls.getMethods();
    for(Method method : methods) {
      if(method.getName().equals(methodName)&& Type.getMethodDescriptor(method).equals(methodDesc)) {
        return method;
      }
    }

    return null;
  }
  private void computeDependence() {
	  
    for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
		      Class<?> clazz = entry.getKey();
		      Map<Method, ReadWriteFields> methodAndReadWrites = entry.getValue();
      
      		//the method dependence map
      		Map<Method, ReadWriteMethods> methodMap = new LinkedHashMap<Method, ReadWriteMethods>();
  
        	methodMap = this.computeRelatedMethod(methodAndReadWrites);
      
        	//put the class and method dependence to the map
        	this.dependences.put(clazz, methodMap);
    }
  } 

  private Map<Method, ReadWriteMethods> computeRelatedMethod(Map<Method, ReadWriteFields> methodAndReadWrites) {
  
    Map<Method, ReadWriteMethods> relatedMethodMap = new LinkedHashMap<Method, ReadWriteMethods>();
    
    //then compute the dependence between each method
    for(Method method : methodAndReadWrites.keySet()) {
    	List<Method> dependentMethods = new LinkedList<Method>();
    	ReadWriteMethods readWrites = new ReadWriteMethods();
      //other methods
      for(Entry<Class<?>, Map<Method, ReadWriteFields>> entry : this.fieldReadWrites.entrySet()) {
    	  Class className=entry.getKey();
    	  Map<Method, ReadWriteFields> methodAndReadWritesClass = entry.getValue();
	      Set<Method> otherMethods = methodAndReadWritesClass.keySet();
	      //otherMethods.remove(method);
	      //the method to keep the relevance value
	      Map<Method, Float> relevanceMap = new LinkedHashMap<Method, Float>();
	      
	      //the list add to the related method map
	      	      
	      for(Method otherMethod : otherMethods) {
    		  if(Filter.isPrivate(otherMethod))
    			  continue;
    		  if(otherMethod.getName().equals(method.getName()))
    			  continue;
	        Set<String> readThisMethod = methodAndReadWrites.get(method).readFields.keySet();
	        Set<String> writeThisMethod = methodAndReadWrites.get(method).writeFields.keySet();
	        Set<String> readOtherMethod = methodAndReadWritesClass.get(otherMethod).readFields.keySet();
	        Set<String> writeOtherMethod = methodAndReadWritesClass.get(otherMethod).writeFields.keySet();
	        
	        //compute the read-read dependence
	        //compute the read-write dependence
	        
	        //it should compute reversely
	        Set<String> readWriteFields = new HashSet<String>();
	        Set<String> readReadFields = new HashSet<String>();
	        for(String readField : readOtherMethod) {
	          if(writeThisMethod.contains(readField)) {
	            readWriteFields.add(readField);
	            readWrites.read(className.getName(),otherMethod.getName());
	          }
	          if(readThisMethod.contains(readField)) {
	            readReadFields.add(readField);
	          }
	        }
	        for(String writeField : writeOtherMethod) {
	            if(writeThisMethod.contains(writeField)) {
	              readWriteFields.add(writeField);
	    	        if(otherMethod == method) 
   	      	         continue;
	    	   //     readWrites.write(className,otherMethod.getName().toString());
	    	        readWrites.write(className.getName(),otherMethod.getName());
	            }
	            if(readThisMethod.contains(writeField)) {
	            	dependentMethods.add(otherMethod); 
//	            	 readWrites.write(otherMethod.getName().toString());
	            	 readWrites.write(className.getName(),otherMethod.getName());
	            }
	    }   
	       
      }
	      
     
      }
   //   relatedMethodMap.put(method, dependentMethods);
      relatedMethodMap.put(method, readWrites);
    }
    
    return relatedMethodMap;
  }
  
}
